package cz.boxdesign.library.web;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.LinkedList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;

import cz.boxdesign.library.common.dao.CommonObjectDao;
import cz.boxdesign.library.common.dao.DatabaseOperationException;
import cz.boxdesign.library.entity.Address;
import cz.boxdesign.library.entity.Book;
import cz.boxdesign.library.entity.Pujceno;
import cz.boxdesign.library.entity.Reservation;
import cz.boxdesign.library.entity.User;
import cz.boxdesign.library.service.AuthorService;
import cz.boxdesign.library.service.BookService;
import cz.boxdesign.library.service.PujcenoService;
import cz.boxdesign.library.service.RezervaceService;
import cz.boxdesign.library.service.UserService;
import cz.boxdesign.library.umgr.web.UmgrController;
import cz.boxdesign.library.web.validation.UserValidator;
import cz.boxdesign.library.web.view.BorrowBookView;
import cz.boxdesign.library.web.view.UserView;

@Controller
@RequestMapping("/users")
public class UserController {

	protected final Log log = LogFactory.getLog(UserController.class);

	@Autowired
	private UmgrController umgrController;

	@Autowired
	@Qualifier(value = "userService")
	private UserService userService;

	@Autowired
	@Qualifier(value = "pujcenoService")
	private PujcenoService pujcenoService;

	@Autowired
	@Qualifier(value = "rezervaceService")
	private RezervaceService rezervaceService;

	@Autowired
	@Qualifier(value = "knihaService")
	private BookService bookService;

	@Autowired
	@Qualifier(value = "autorService")
	private AuthorService authorService;

	@Autowired
	@Qualifier("commonObjectDao")
	private CommonObjectDao commonObjectDao;

	@InitBinder
	public void initModel(WebDataBinder binder) {
		Object targer = binder.getTarget();
		if (targer instanceof UserView) {
			if (binder.getValidator() == null) {
				binder.setValidator(new UserValidator());
			}
		}
	}

	@RequestMapping(method = RequestMethod.GET)
	public ModelAndView showList() {
		ModelAndView model = new ModelAndView("users/showList");
		try {
			model.addObject("user", new UserView());
			model.addObject("list", userService.getAll());
			log.info("Redirecting to user/showList");
		} catch (DatabaseOperationException e) {
			log.error(e.getMessage(), e);
			model.addObject("error", e.getMessage());
		}
		return model;
	}

	/**
	 * 
	 * @param userView
	 * @param request
	 * @return
	 */
	@RequestMapping(value = "/edit", method = RequestMethod.GET)
	public ModelAndView editUser(HttpServletRequest request) {
		ModelAndView model = new ModelAndView();
		insertOrUpdate(request, model);
		return model;
	}

	/**
	 * 
	 * @param userView
	 * @param request
	 * @return
	 */
	@RequestMapping(value = "/show", method = RequestMethod.GET)
	public ModelAndView showUser(HttpServletRequest request) {
		HttpSession session = request.getSession();
		List<GrantedAuthority> auth = (List<GrantedAuthority>) session.getAttribute("role");
		Integer userId = Integer.parseInt(session.getAttribute("userId").toString());
		Integer requestedUserId = Integer.parseInt(request.getParameter("userId"));
		if (auth != null && userId != null && !auth.isEmpty()) {
			System.out.println(auth.get(0).getAuthority());
			if (auth.get(0).getAuthority().equals("ROLE_USER") && userId.intValue() == requestedUserId.intValue()) {
				return getUserDetails(request);
			} else if (auth.get(0).getAuthority().equals("ROLE_ADMIN")) {
				return getUserDetails(request);
			} else {
				return umgrController.unauthorizedAccess(request);
			}
		} else {
			return umgrController.unauthorizedAccess(request);
		}
	}

	private ModelAndView getUserDetails(HttpServletRequest request) {
		ModelAndView model = new ModelAndView("users/show");
		try {
			Integer userId = Integer.parseInt(request.getParameter("userId"));
			User u = userService.getById(userId);
			UserView userView = new UserView();
			copyUserToView(u, userView);
			model.addObject("user", userView);
			List<Pujceno> pujcenoList = pujcenoService.getByClause("and id_u = " + userId);
			List<Book> pujceneKnihy = new ArrayList<Book>();
			for (Pujceno pujceno : pujcenoList) {
				pujceneKnihy.add(pujceno.getKniha());
			}

			List<Reservation> rezervaceList = rezervaceService.getByClause("and uzivatel_id_u = " + userId);
			List<Book> rezervovaneKnihy = new ArrayList<Book>();
			for (Reservation reservation : rezervaceList) {
				rezervovaneKnihy.add(reservation.getKniha());
			}
			model.addObject("pujceneKnihy", pujceneKnihy);
			model.addObject("rezervovaneKnihy", rezervovaneKnihy);
		} catch (DatabaseOperationException e) {
			e.printStackTrace();
			model.addObject("error", "Ups .. An Error occurs. Please try again ! ");
		}
		return model;
	}

	@RequestMapping(value = "/prolong", method = RequestMethod.GET)
	public ModelAndView prolongRegistration(HttpServletRequest request) {
		try {
			Integer userId = Integer.valueOf(request.getParameter("userId"));
			commonObjectDao.prolongAccount(userId);
			return new ModelAndView(new RedirectView("../users.do"));
		} catch (DatabaseOperationException e) {
			ModelAndView model = new ModelAndView(new RedirectView("../users.do"));
			model.addObject("error", "Ups .. An Error occurs. Please try again ! ");
			return model;
		}
	}

	@RequestMapping(value = "/borrowFromReservation", method = RequestMethod.GET)
	public ModelAndView borrowFromReservation(HttpServletRequest request, @RequestParam(required = true, value = "userId") Integer user,
			@RequestParam(required = true, value = "bookId") Integer book) {
		try {
			commonObjectDao.borrowReservedBook(user, book);
			return new ModelAndView(new RedirectView("show.do?userId=" + user));
		} catch (DatabaseOperationException e) {
			ModelAndView model = new ModelAndView(new RedirectView("../users.do"));
			model.addObject("error", "Ups .. An Error occurs. Please try again ! ");
			return model;
		}
	}

	@RequestMapping(value = "/borrowBook", method = RequestMethod.GET)
	public ModelAndView borrowBook(HttpServletRequest request) {
		try {
			List<User> userList = userService.getByClause("and is_admin = 0");
			List<Book> bookList = bookService.getAll();
			ModelAndView model = new ModelAndView("users/borrowBook");
			BorrowBookView view = new BorrowBookView();
			view.setBookList(bookList);
			view.setUserList(userList);
			model.addObject("borrowView", view);
			HttpSession session = request.getSession();
			if(session.getAttribute("message")!=null){
				model.addObject("message", session.getAttribute("message"));
				session.removeAttribute("message");
			}
			return model;
		} catch (DatabaseOperationException e) {
			ModelAndView model = new ModelAndView(new RedirectView("../books.do"));
			model.addObject("error", "Ups .. An Error occurs. Please try again ! ");
			return model;
		}
	}

	@RequestMapping(value = "/submitBorrowedBook", method = RequestMethod.POST)
	public ModelAndView submitBorrowedBook(BorrowBookView view, HttpServletRequest request) {
		try {
			List<Book> bookList = bookService.getByClause(getInClause(view.getBookIds()));
			List<Pujceno> borrowList = new ArrayList<Pujceno>();
			User u = userService.getById(view.getUserId());
			Calendar pujcenoOd = Calendar.getInstance();
			Calendar pujcenoDo = Calendar.getInstance();
			pujcenoDo.add(Calendar.MONTH, 1);
			for (Book book : bookList) {
				Pujceno p = new Pujceno();
				p.setUser(u);
				p.setKniha(book);
				p.setPujcenoDo(new java.sql.Date(pujcenoDo.getTimeInMillis()));
				p.setPujcenoOd(new java.sql.Date(pujcenoOd.getTimeInMillis()));
				borrowList.add(p);
			}
			List<Pujceno> alreayBorrowed = pujcenoService.getByClause("and id_u = " + u.getId());
			for (Pujceno pujceno : alreayBorrowed) {
				for (Pujceno pujceno2 : borrowList) {
					if (pujceno.getUser().getId().equals(pujceno2.getUser().getId())) {
						borrowList.remove(pujceno2);
						break;
					}
				}
			}
			pujcenoService.batchInsert(borrowList);
			request.getSession().setAttribute("message", "Book was sucessfully borrowed !");
			return new ModelAndView(new RedirectView("borrowBook.do"));
		} catch (DatabaseOperationException e) {
			ModelAndView model = new ModelAndView(new RedirectView("books.do"));
			model.addObject("error", "Ups .. An Error occurs. Please try again ! ");
			return model;
		}
	}

	private String getInClause(List<Integer> list) {
		StringBuilder sb = new StringBuilder("and id in (");
		for (int i = 0; i < list.size(); i++) {
			if (i == (list.size() - 1)) {
				sb.append(list.get(i) + ")");
			} else {
				sb.append(list.get(i) + ",");
			}
		}
		return sb.toString();
	}

	@RequestMapping(value = "/giveBack", method = RequestMethod.GET)
	public ModelAndView giveBack(HttpServletRequest request, @RequestParam(required = true, value = "userId") Integer user,
			@RequestParam(required = true, value = "bookId") Integer book) {
		try {
			Pujceno pujceno = new Pujceno();
			pujceno.setKniha(bookService.getById(book));
			pujceno.setUser(userService.getById(user));
			pujcenoService.deleteEntity(pujceno);
			return new ModelAndView(new RedirectView("show.do?userId=" + user));
		} catch (DatabaseOperationException e) {
			ModelAndView model = new ModelAndView(new RedirectView("../users.do"));
			model.addObject("error", "Ups .. An Error occurs. Please try again ! ");
			return model;
		}
	}

	@RequestMapping(value = "/deleteReservation", method = RequestMethod.GET)
	public ModelAndView deleteReservation(HttpServletRequest request, @RequestParam(required = true, value = "userId") Integer user,
			@RequestParam(required = true, value = "bookId") Integer book) {
		try {
			Reservation reservation = new Reservation();
			reservation.setKniha(bookService.getById(book));
			reservation.setUser(userService.getById(user));
			rezervaceService.deleteEntity(reservation);
			return new ModelAndView(new RedirectView("show.do?userId=" + user));
		} catch (DatabaseOperationException e) {
			ModelAndView model = new ModelAndView(new RedirectView("../users.do"));
			model.addObject("error", "Ups .. An Error occurs. Please try again ! ");
			return model;
		}
	}

	@RequestMapping(value = "/makeReservation", method = RequestMethod.GET)
	public ModelAndView doreservation(HttpServletRequest request, @RequestParam(required = true, value = "userId") Integer user,
			@RequestParam(required = true, value = "bookId") Integer book) {
		try {
			if (rezervaceService
					.getByClause("and " + Reservation.USER_COLUMN + "=" + user + " and " + Reservation.BOOK_COLUMN + "=" + book).isEmpty()) {
				log.info(user + "  " + book);
				Reservation reservation = new Reservation();
				reservation.setKniha(bookService.getById(book));
				reservation.setUser(userService.getById(user));
				rezervaceService.insertEntity(reservation);
				request.getSession().setAttribute("message", "Reservation successful !");
				return new ModelAndView(new RedirectView("/isLibrary/books.do"));
			}
		} catch (DatabaseOperationException e) {
			ModelAndView model = new ModelAndView(new RedirectView("../users.do"));
			model.addObject("error", "Ups .. An Error occurs. Please try again ! ");
			return model;
		}
		request.getSession().setAttribute("message", "Reservation already exists !");
		return new ModelAndView(new RedirectView("/isLibrary/books.do"));
	}

	/**
	 * 
	 * @return
	 */
	@RequestMapping(value = "/delete", method = RequestMethod.GET)
	public ModelAndView deleteUser(HttpServletRequest request, @RequestParam("userId") Integer userId) {
		ModelAndView model = new ModelAndView();
		try {
			User user = userService.getById(userId);
			userService.deleteEntity(user);
		} catch (DatabaseOperationException e) {
			model.addObject("error", "Ups .. An Error occurs. Please try again ! ");
		}
		return new ModelAndView(new RedirectView("../users.do"));
	}

	/**
	 * 
	 * @return
	 */
	@RequestMapping(value = "/submit", method = RequestMethod.POST)
	public ModelAndView submitUser(HttpServletRequest request, @Validated UserView userView, BindingResult error, ModelAndView model) {
		List<Error> errorList = validate(userView, request.getParameter("userId") != null);
		if (errorList.isEmpty() || !error.hasErrors()) {
			User user = new User();
			if (request.getParameter("userId") != null) {// update
				try {
					copyUserFromView(user, userView);
					userService.updateEntity(user);
					user = userService.getByClause("and login_u ='" + userView.getLogin() + "'").get(0);
					request.setAttribute("userId", user.getId());
					return showUser(request);
				} catch (DatabaseOperationException e) {
					model.addObject("error", "Ups .. An Error occurs. Please try again ! ");
					return new ModelAndView(new RedirectView("../users.do"));
				}
			} else { // insert
				try {
					copyUserFromView(user, userView);
					Calendar cal = Calendar.getInstance();
					user.setDatumRegistrace(new java.sql.Date(cal.getTimeInMillis()));
					user.setObdobiOd(new java.sql.Date(cal.getTimeInMillis()));
					user.setDatumRegistrace(new java.sql.Date(cal.getTimeInMillis()));
					cal.add(Calendar.MONTH, 1);
					user.setObdobiDo(new java.sql.Date(cal.getTimeInMillis()));

					userService.insertEntity(user);
					user = userService.getByClause("and login_u='" + userView.getLogin() + "'").get(0);
					return new ModelAndView(new RedirectView("show.do?userId=" + user.getId()));
				} catch (DatabaseOperationException e) {
					model.addObject("error", "Ups .. An Error occurs. Please try again ! ");
					return new ModelAndView(new RedirectView("../users.do"));
				}
			}
		} else {
			model.addObject("formErrors", errorList.isEmpty() ? null : errorList);
			model.addObject("user", userView);
			if (request.getParameter("userId") != null) {
				return new ModelAndView(new RedirectView("edit.do?userId="+request.getParameter("userId")));
			} else {
				request.setAttribute("user", model);
				return newUser(userView, request, model);
			}
		}

	}

	@RequestMapping("/findByLogin")
	public ModelAndView findUserByLogin(UserView view) {
		ModelAndView model = new ModelAndView("users/showList");
		model.addObject("user", new UserView());
		List<User> list = new ArrayList<User>();
		if (view.getLogin() != null && !view.getLogin().isEmpty()) {
			model.addObject("list", list = userService.getByClause("and login_u like '%" + view.getLogin() + "%' and is_admin=0"));
		} else {
			model.addObject("list", userService.getByClause("and is_admin=0"));
			model.addObject("error1", new Error("Field Login is requiered !"));
			return model;
		}
		if(list.isEmpty()){
			model.addObject("error3", new Error("No records found !"));
			model.addObject("list", userService.getByClause("and is_admin=0"));
		}
		return model;
	}

	@RequestMapping("/findByName")
	public ModelAndView findUserByName(UserView view) {
		ModelAndView model = new ModelAndView("users/showList");
		model.addObject("user", new UserView());
		List<User> list = new ArrayList<User>();
		if (view.getJmeno() != null && !view.getJmeno().isEmpty() && view.getPrijmeni() != null && !view.getPrijmeni().isEmpty()) {
			model.addObject("list",
					list = userService.getByClause("and upper(jm_u) like '%" + view.getJmeno().toUpperCase() + "%' and upper(prijmeni_u) like '%" + view.getPrijmeni().toUpperCase() + "%'  and is_admin=0"));
		}
		if(view.getJmeno() == null || view.getJmeno().isEmpty() || view.getPrijmeni() == null || view.getPrijmeni().isEmpty()){
			model.addObject("error2", new Error("Both Fields are requiered !"));
			model.addObject("list", userService.getByClause("and is_admin=0"));
			return model;
		}
		if(list.isEmpty()){
			model.addObject("error2", new Error("No records found !"));
			model.addObject("list", userService.getByClause("and is_admin=0"));
		}
		return model;
	}

	/**
	 * 
	 * @param userView
	 * @param request
	 * @return
	 */
	@RequestMapping(value = "/new", method = RequestMethod.GET)
	public ModelAndView newUser(UserView userView, HttpServletRequest request, ModelAndView model) {
		model = request.getAttribute("user") != null ? (ModelAndView) request.getAttribute("user") : new ModelAndView();
		insertOrUpdate(request, model);
		return model;
	}

	/**
	 * @param request
	 * @param model
	 */
	private void insertOrUpdate(HttpServletRequest request, ModelAndView model) {
		UserView userView = model.getModelMap().get("user") != null ? (UserView) model.getModelMap().get("user") : new UserView();
		if (request.getParameter("userId") != null) {
			Integer userId = Integer.valueOf(request.getParameter("userId"));
			User user = userService.getById(userId);
			BeanUtils.copyProperties(user, userView);
			BeanUtils.copyProperties(user.getAdress(), userView);
			model.setViewName("users/edit");
		} else {
			model.setViewName("users/new");
		}
		model.addObject("user", userView);
	}

	private void copyUserToView(User user, UserView userView) {
		userView.setMesto(user.getAdress().getMesto());
		userView.setPsc(user.getAdress().getPsc());
		userView.setUlice(user.getAdress().getUlice());
		userView.setJmeno(user.getJmeno());
		userView.setPrijmeni(user.getPrijmeni());
		userView.setLogin(user.getLogin());
		userView.setPassword(user.getPassword());
		userView.setPlat(0d);
		userView.setId(user.getId());
		userView.setLogin(user.getLogin());
		userView.setObdobiDo(user.getObdobiDo());
		userView.setObdobiOd(user.getObdobiOd());
	}

	private void copyUserFromView(User user, UserView view) {
		Address address = new Address();
		address.setMesto(view.getMesto());
		address.setPsc(view.getPsc());
		address.setUlice(view.getUlice());
		user.setAdress(address);
		user.setJmeno(view.getJmeno());
		user.setPrijmeni(view.getPrijmeni());
		user.setLogin(view.getLogin());
		user.setPassword(view.getPassword());
		user.setPlat(0d);
		user.setId(view.getId());
		user.setLogin(view.getLogin());
		user.setAdmin(view.getAdmin());
	}

	private List<Error> validate(UserView view, Boolean isEdit) throws IllegalArgumentException {
		List<Error> errorList = new LinkedList<Error>();
		String psc = view.getPsc();
		if (psc.contains(" ")) {
			psc = psc.replace(" ", "");
		}
		try {
			Integer pscint = Integer.valueOf(psc);
			if (view.getPsc().length() > 6) {
				errorList.add(new Error("error.psc.wrong.format"));
			}
		} catch (NumberFormatException e) {
			errorList.add(new Error("error.psc.wrong.format"));
		}
		if (!isEdit) {
			if (commonObjectDao.isLoginAvailabel(view.getLogin())) {
				errorList.add(new Error("error.login.exist"));
			}
		}
		return errorList;
	}
}
